﻿using System;
using System.Collections.Generic;

namespace Behaviours.Events
{
  public class EventDispatcher
  {
    private List<Action<Event>> listenersWrappers = new List<Action<Event>>();
    private List<object> listenersCallBack = new List<object>();
    private List<string> listenersType = new List<string>();
    private static List<EventDispatcher> dispatchers = new List<EventDispatcher>();

    public void On<T>(object eventName, Action<T> callBack) where T : Event => this.AddEventListener<T>(eventName, callBack);

    public void AddEventListener<T>(object eventName, Action<T> callBack) where T : Event
    {
      this.listenersType.Add(eventName.ToString());
      this.listenersWrappers.Add((Action<Event>) (x => callBack((T) x)));
      this.listenersCallBack.Add((object) callBack);
      if (this.listenersCallBack.Count != 1)
        return;
      EventDispatcher.dispatchers.Add(this);
    }

    public void Off<T>(object eventName, Action<T> callBack) where T : Event => this.RemoveEventListener<T>(eventName, callBack);

    public void RemoveEventListener<T>(object eventName, Action<T> callBack) where T : Event
    {
      int count = this.listenersType.Count;
      for (int index = 0; index < count; ++index)
      {
        if (this.listenersType[index] == eventName.ToString() && ((Delegate) this.listenersCallBack[index]).Method == callBack.Method)
        {
          this.listenersType.RemoveAt(index);
          this.listenersWrappers.RemoveAt(index);
          this.listenersCallBack.RemoveAt(index);
          if (this.listenersCallBack.Count != 0)
            break;
          EventDispatcher.dispatchers.Remove(this);
          break;
        }
      }
    }

    public void DispatchEvent(Event evt) => this.DispatchTargetedEvent(evt);

    public void RemoveAllEventListeners(object eventName = null)
    {
      int count = this.listenersType.Count;
      for (int index = 0; index < count; ++index)
      {
        if (eventName == null || this.listenersType[index] == eventName.ToString())
        {
          this.listenersType.RemoveAt(index);
          this.listenersWrappers.RemoveAt(index);
          this.listenersCallBack.RemoveAt(index);
          --index;
          --count;
        }
      }
      if (this.listenersCallBack.Count != 0)
        return;
      EventDispatcher.dispatchers.Remove(this);
    }

    public bool HasEventListener(object eventName)
    {
      int count = this.listenersType.Count;
      for (int index = 0; index < count; ++index)
      {
        if (this.listenersType[index] == eventName.ToString())
          return true;
      }
      return false;
    }

    public static void RemoveAllListeners(List<EventDispatcher> excludes = null)
    {
      while (EventDispatcher.dispatchers.Count > 0)
      {
        if (excludes == null || !excludes.Contains(EventDispatcher.dispatchers[0]))
          EventDispatcher.dispatchers[0].RemoveAllEventListeners();
      }
    }

    internal void DispatchTargetedEvent(Event e, object target = null)
    {
      e.Target = target != null ? target : (object) this;
      int count1 = this.listenersType.Count;
      List<Action<Event>> actionList = new List<Action<Event>>();
      for (int index = 0; index < count1; ++index)
      {
        if (this.listenersType[index] == e.Name)
          actionList.Add(this.listenersWrappers[index]);
      }
      int count2 = actionList.Count;
      for (int index = 0; index < count2; ++index)
        actionList[index](e);
    }
  }
}
